/*
 * Este programa es software libre; usted puede redistribuirlo y/o modificarlo bajo los terminos
 * de la licencia "GNU General Public License" publicada por la Fundacion "Free Software Foundation".
 * Este programa se distribuye con la esperanza de que pueda ser util, pero SIN NINGUNA GARANTIA;
 * vea la licencia "GNU General Public License" para obtener mas informacion.
 */
package adalid.core;

import adalid.commons.TLB;
import adalid.commons.interfaces.Wrappable;
import adalid.commons.interfaces.Wrapper;
import adalid.core.expressions.VariantX;
import adalid.core.interfaces.Artifact;
import adalid.core.interfaces.DataArtifact;
import adalid.core.interfaces.Entity;
import adalid.core.interfaces.Expression;
import adalid.core.interfaces.PersistentEntity;
import adalid.core.wrappers.ArtifactWrapper;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * @author Jorge Campins
 */
public abstract class AbstractArtifact implements Artifact, Wrappable {

    /**
     *
     */
    private static final Logger logger = Logger.getLogger(Artifact.class);

    protected static final String EOL = "\n";

    protected static final String TAB = "\t";

    // <editor-fold defaultstate="collapsed" desc="field declarations">
    /**
     *
     */
    private boolean _declared;

    /**
     *
     */
    private Boolean _inherited;

    /**
     *
     */
    private String _name;

    /**
     *
     */
    private String _alias;

    /**
     *
     */
    private String _sqlName;

    /**
     *
     */
    private String _defaultLabel;

    /**
     *
     */
    private String _defaultShortLabel;

    /**
     *
     */
    private String _defaultCollectionLabel;

    /**
     *
     */
    private String _defaultCollectionShortLabel;

    /**
     *
     */
    private String _defaultDescription;

    /**
     *
     */
    private String _defaultShortDescription;

    /**
     *
     */
    private String _defaultTooltip;

    /**
     *
     */
    private Artifact _declaringArtifact;

    /**
     *
     */
    private Field _declaringField;

    /**
     *
     */
    private int _declaringFieldIndex;

    /**
     *
     */
    private int _depth;

    /**
     *
     */
    private int _round;

    /**
     *
     */
    private Map<Class<? extends Annotation>, Field> _annotations = new LinkedHashMap<>();
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="field getters and setters">
    /**
     * @return true if the artifact is declared
     */
    @Override
    public boolean isDeclared() {
        return _declared;
    }

    /**
     * @return true if the artifact is not declared
     */
    @Override
    public boolean isNotDeclared() {
        return !_declared;
    }

    /**
     *
     */
    void setDeclared(String name) {
        setDeclared(name, null);
    }

    /**
     *
     */
    void setDeclared(String name, Artifact declaringArtifact) {
        setDeclared(name, declaringArtifact, null);
    }

    /**
     *
     */
    void setDeclared(String name, Artifact declaringArtifact, Field declaringField) {
        setDeclared(name, declaringArtifact, declaringField, 0);
    }

    /**
     *
     */
    void setDeclared(String name, Artifact declaringArtifact, Field declaringField, int declaringFieldIndex) {
        if (_declared) {
            return;
        }
        setName(name);
        setDeclaringArtifact(declaringArtifact);
        setDeclaringField(declaringField);
        setDeclaringFieldIndex(declaringFieldIndex);
        initializeAnnotations();
        annotate(getNamedClass());
        annotate(getDeclaringField());
        _declared = true;
    }

    /**
     * @return true if the artifact is inherited
     */
    @Override
    public boolean isInherited() {
        return _declared && inherited();
    }

    /**
     * @return true if the artifact is not inherited
     */
    @Override
    public boolean isNotInherited() {
        return _declared && !inherited();
    }

    private boolean inherited() {
        if (_inherited == null) {
            Field declaringField = getDeclaringField();
            Artifact declaringArtifact = getDeclaringArtifact();
            Class<?> declaringFieldClass = declaringField.getDeclaringClass();
            Class<?> declaringFieldNamedClass = XS1.getNamedClass(declaringFieldClass);
            Class<?> declaringArtifactNamedClass = XS1.getNamedClass(declaringArtifact);
            String declaringFieldNamedClassSimpleName = declaringFieldNamedClass.getSimpleName();
            String declaringArtifactNamedClassSimpleName = declaringArtifactNamedClass.getSimpleName();
            _inherited = !declaringFieldNamedClassSimpleName.equals(declaringArtifactNamedClassSimpleName);
        }
        return _inherited;
    }

    /**
     * @return the name
     */
    @Override
    public String getName() {
        return _name;
    }

    /**
     * @param name the name to set
     */
    void setName(String name) {
        if (_declared) {
            return;
        }
        _name = name;
        if (_alias == null) {
            _alias = name;
        }
    }

    /**
     * @return the alias
     */
    @Override
    public String getAlias() {
        return _alias;
    }

    /**
     * @param alias the alias to set
     */
    @Override
    public void setAlias(String alias) {
        _alias = alias;
    }

    /**
     * @return the SQL name
     */
    @Override
    public String getSqlName() {
        return _sqlName;
    }

    /**
     * @param sqlName the SQL name to set
     */
    @Override
    public void setSqlName(String sqlName) {
        _sqlName = sqlName;
    }

    /**
     * @return the default label
     */
    @Override
    public String getDefaultLabel() {
        return _defaultLabel;
    }

    /**
     * @param defaultLabel the default label to set
     */
    @Override
    public void setDefaultLabel(String defaultLabel) {
        _defaultLabel = defaultLabel;
        _defaultShortLabel = defaultLabel;
    }

    /**
     * @return the default short label
     */
    @Override
    public String getDefaultShortLabel() {
        return _defaultShortLabel;
    }

    /**
     * @param defaultShortLabel the default short label to set
     */
    @Override
    public void setDefaultShortLabel(String defaultShortLabel) {
        _defaultShortLabel = defaultShortLabel;
    }

    /**
     * @return the default collection label
     */
    @Override
    public String getDefaultCollectionLabel() {
        return _defaultCollectionLabel;
    }

    /**
     * @param defaultCollectionLabel the default collection label to set
     */
    @Override
    public void setDefaultCollectionLabel(String defaultCollectionLabel) {
        _defaultCollectionLabel = defaultCollectionLabel;
        _defaultCollectionShortLabel = defaultCollectionLabel;
    }

    /**
     * @return the default collection short label
     */
    @Override
    public String getDefaultCollectionShortLabel() {
        return _defaultCollectionShortLabel;
    }

    /**
     * @param defaultCollectionShortLabel the default collection short label to set
     */
    @Override
    public void setDefaultCollectionShortLabel(String defaultCollectionShortLabel) {
        _defaultCollectionShortLabel = defaultCollectionShortLabel;
    }

    /**
     * @return the default description
     */
    @Override
    public String getDefaultDescription() {
        return _defaultDescription;
    }

    /**
     * @param defaultDescription the default description to set
     */
    @Override
    public void setDefaultDescription(String defaultDescription) {
        _defaultDescription = defaultDescription;
        _defaultShortDescription = defaultDescription;
    }

    /**
     * @return the default short description
     */
    @Override
    public String getDefaultShortDescription() {
        return _defaultShortDescription;
    }

    /**
     * @param defaultShortDescription the default short description to set
     */
    @Override
    public void setDefaultShortDescription(String defaultShortDescription) {
        _defaultShortDescription = defaultShortDescription;
    }

    /**
     * @return the default tooltip
     */
    @Override
    public String getDefaultTooltip() {
        return _defaultTooltip;
    }

    /**
     * @param defaultTooltip the default tooltip to set
     */
    @Override
    public void setDefaultTooltip(String defaultTooltip) {
        _defaultTooltip = defaultTooltip;
    }

    /**
     * @return the declaring artifact
     */
    @Override
    public Artifact getDeclaringArtifact() {
        return _declaringArtifact;
    }

    String getDeclaringArtifactClassName() {
        return _declaringArtifact == null ? null : _declaringArtifact.getClass().getName();
    }

    /**
     * @param declaringArtifact the declaring artifact to set
     */
    void setDeclaringArtifact(Artifact declaringArtifact) {
        if (_declared) {
            return;
        }
        resetDeclaringArtifact(declaringArtifact);
    }

    void resetDeclaringArtifact(Artifact declaringArtifact) {
        _declaringArtifact = declaringArtifact;
        if (declaringArtifact != null) {
            if (this instanceof Entity) {
                _depth = declaringArtifact.depth() + 1;
                _round = XS1.round(getNamedClass(), declaringArtifact);
            } else {
                _depth = declaringArtifact.depth();
            }
        }
    }

    /**
     * @return the declaring field
     */
    @Override
    public Field getDeclaringField() {
        return _declaringField;
    }

    /**
     * @param declaringField the declaring field to set
     */
    void setDeclaringField(Field declaringField) {
        if (_declared) {
            return;
        }
        resetDeclaringField(declaringField);
    }

    void resetDeclaringField(Field declaringField) {
        _declaringField = declaringField;
    }

    /**
     * @return the declaring field index
     */
    @Override
    public int getDeclaringFieldIndex() {
        return _declaringFieldIndex;
    }

    /**
     * @param declaringFieldIndex the declaring field index to set
     */
    void setDeclaringFieldIndex(int declaringFieldIndex) {
        if (_declared) {
            return;
        }
        _declaringFieldIndex = declaringFieldIndex;
    }

    /**
     * @return the declaring entity if the artifact directly declared by one, null otherwise
     */
    @Override
    public Entity getDeclaringEntity() {
        return _declaringArtifact instanceof Entity ? (Entity) _declaringArtifact : null;
    }

    /**
     * @return the declaring entity if the artifact directly declared by one, null otherwise
     */
    @Override
    public PersistentEntity getDeclaringPersistentEntity() {
        Entity declaringEntity = getDeclaringEntity();
        return declaringEntity instanceof PersistentEntity ? (PersistentEntity) declaringEntity : null;
    }

    /**
     * @return the declaring entity if the artifact directly declared by one, null otherwise
     */
    @Override
    public Entity getDeclaringEntityRoot() {
        Entity declaringEntity = getDeclaringEntity();
        return declaringEntity == null ? null : declaringEntity.getRoot();
    }

    /**
     * @return the declaring entity if the artifact directly declared by one, null otherwise
     */
    @Override
    public PersistentEntity getDeclaringPersistentEntityRoot() {
        Entity declaringEntityRoot = getDeclaringEntityRoot();
        return declaringEntityRoot instanceof PersistentEntity ? (PersistentEntity) declaringEntityRoot : null;
    }

    /**
     * @return the declaring entity if the artifact directly declared by one, null otherwise
     */
    @Override
    public Entity getDeclaringFieldEntityRoot() {
        Class<?> declaringClass = _declaringField == null ? null : _declaringField.getDeclaringClass();
        Entity declaringEntity = declaringClass == null ? null : getDeclaringEntity();
        return declaringEntity == null ? null : declaringEntity.getDeclaringProject().getEntity(declaringClass);
    }

    /**
     * @return the declaring entity if the artifact directly declared by one, null otherwise
     */
    @Override
    public PersistentEntity getDeclaringFieldPersistentEntityRoot() {
        Entity declaringFieldEntityRoot = getDeclaringFieldEntityRoot();
        return declaringFieldEntityRoot instanceof PersistentEntity ? (PersistentEntity) declaringFieldEntityRoot : null;
    }

    /**
     * @return the declaring operation if the artifact directly declared by one, null otherwise
     */
    @Override
    public Operation getDeclaringOperation() {
        return _declaringArtifact instanceof Operation ? (Operation) _declaringArtifact : null;
    }

    /**
     * @return the depth
     */
    @Override
    public int depth() {
        return _depth;
    }

    /**
     * @return the round
     */
    @Override
    public int round() {
        return _round;
    }

    /**
     * @return the annotations map
     */
    Map<Class<? extends Annotation>, Field> getAnnotations() {
        return _annotations;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="annotate">
    void initializeAnnotations() {
    }

    void annotate(Class<?> type) {
        if (type == null) {
            return;
        }
        Annotation[] annotations = type.getAnnotations();
        List<Class<? extends Annotation>> valid = getValidTypeAnnotations();
        checkAnnotations(type, annotations, valid);
    }

    void annotate(Field field) {
        if (field == null) {
            return;
        }
        Annotation[] annotations = field.getAnnotations();
        List<Class<? extends Annotation>> valid = getValidFieldAnnotations();
        checkAnnotations(field, annotations, valid);
    }

    private void checkAnnotations(Object object, Annotation[] annotations, List<Class<? extends Annotation>> valid) {
        Project project = TLC.getProject();
//      if (project == null) {
//          return;
//      }
        String pattern = "@{0} is not valid for {1}; annotation ignored";
        String message;
        Class<? extends Annotation> annotationType;
        for (Annotation annotation : annotations) {
            annotationType = annotation.annotationType();
            if (valid.contains(annotationType)) {
                continue;
            }
            message = MessageFormat.format(pattern, annotationType.getSimpleName(), object);
            project.getParser().log(Level.ERROR, message);
            project.getParser().increaseErrorCount();
        }
    }

    protected List<Class<? extends Annotation>> getValidTypeAnnotations() {
        return new ArrayList<>();
    }

    protected List<Class<? extends Annotation>> getValidFieldAnnotations() {
        return new ArrayList<>();
    }

    @Override
    public Field put(Class<? extends Annotation> annotation, Field field) {
        XS1.checkAccess();
        return _annotations.put(annotation, field);
    }
    // </editor-fold>

    /**
     * @return the class path
     */
    @Override
    public String getClassPath() {
        return classPath(this);
    }

    private String classPath(Artifact a) {
        Artifact da = a.getDeclaringArtifact();
        String str1 = a.getName();
        String str2 = XS1.getNamedClass(a).getSimpleName();
        String str3 = str1 == null || str1.equals(str2) ? str2 : str2 + "[" + str1 + "]";
        return da == null ? str3 : classPath(da) + "." + str3;
    }

    /**
     * @return true if type is present in the class path
     */
    @Override
    public boolean isClassInPath(Class<?> type) {
        return isClassInPath(type, this);
    }

    private boolean isClassInPath(Class<?> clazz, Artifact artifact) {
        if (clazz != null) {
            if (artifact != null) {
                if (clazz.isAssignableFrom(XS1.getNamedClass(artifact))) {
                    return true;
                }
                Artifact declaringArtifact = artifact.getDeclaringArtifact();
                if (declaringArtifact != null) {
                    return isClassInPath(clazz, declaringArtifact);
                }
            }
        }
        return false;
    }

    /**
     * @return the path
     */
    @Override
    @SuppressWarnings("unchecked") // unchecked conversion
    public List<Artifact> getPathList() {
        List list = new ArrayList<>();
        addToPathList(list, this);
        return list;
    }

    private void addToPathList(List<Artifact> list, Artifact artifact) {
        Artifact declaringArtifact = artifact.getDeclaringArtifact();
        if (declaringArtifact != null) {
            addToPathList(list, declaringArtifact);
        }
        list.add(artifact);
    }

    /**
     * @return the path string
     */
    @Override
    public String getPathString() {
        return path(this);
    }

    private String path(Artifact a) {
        String p;
        if (a.getDeclaringField() == null || a.getDeclaringArtifact() == null) {
//          p = "_" + a.getAlias();
            p = XS1.getNamedClass(a).getSimpleName() + "." + "this";
        } else {
            p = path(a.getDeclaringArtifact()) + "." + a.getDeclaringField().getName();
            if (a.getDeclaringField().getType().isArray()) {
                p += "[" + a.getDeclaringFieldIndex() + "]";
            }
            p = StringUtils.removeStart(p, ".");
        }
        return p;
    }

    /**
     * @return the full name
     */
    @Override
    public String getFullName() {
        return StringUtils.remove(getPathString(), "." + "this");
    }

    protected String getValueString(Object value) {
        if (value == null) {
            return null;
        } else if (value instanceof Artifact) {
            Artifact artifact = (Artifact) value;
            return getValueString(value, artifact.getPathString());
        } else {
//          return getValueString(value, value);
            return value.toString();
        }
    }

    protected String getValueString(Object object, Object value) {
        return XS1.getNamedClass(object).getSimpleName() + "(" + value.toString() + ")";
    }

    /**
     * @return the named class
     */
    Class<?> getNamedClass() {
        return XS1.getNamedClass(this);
    }

    /**
     * @return true if is a Expression; otherwise false
     */
    @Override
    public boolean isExpression() {
        return this instanceof Expression;
    }

    protected void verifyExpression(Expression expression) {
        if (expression instanceof VariantX && depth() == 0) {
            Artifact artifact = (Artifact) expression;
            String expname = StringUtils.isBlank(expression.getName()) ? "" : expression.getName() + " ";
            String message = "invalid expression " + expname + "at " + getFullName();
            message += "; expression " + artifact.getFullName() + " is not properly defined";
            logger.error(message);
            TLC.getProject().getParser().increaseErrorCount();
        } else {
            verifyExpression(expression, this);
        }
    }

    private void verifyExpression(Expression expression, Artifact container) {
        if (expression != null && container != null) {
            Artifact artifact;
            List<Artifact> pathList;
            Expression operandExpression;
            String fullName;
            String containerFullName = container.getFullName();
            String expname = StringUtils.isBlank(expression.getName()) ? "" : expression.getName() + " ";
            String message = "invalid expression " + expname + "at " + containerFullName;
            Object[] operands = expression.getOperands();
            if (operands != null && operands.length > 0) {
                for (Object operand : operands) {
                    if (operand instanceof DataArtifact) {
                        artifact = (Artifact) operand;
                        pathList = artifact.getPathList();
                        if (pathList.contains(container)) {
                            continue;
                        }
                        fullName = artifact.getFullName();
                        message += "; operand " + fullName + " is out of scope";
                        logger.error(message);
                        TLC.getProject().getParser().increaseErrorCount();
                    } else if (operand instanceof VariantX) {
                        artifact = (Artifact) operand;
                        fullName = artifact.getFullName();
                        message += "; operand " + fullName + " is out of scope";
                        logger.error(message);
                        TLC.getProject().getParser().increaseErrorCount();
                    } else if (operand instanceof Expression) {
                        operandExpression = (Expression) operand;
                        verifyExpression(operandExpression, container);
                    }
                }
            }
        }
    }

    /**
     * the wrapper
     */
    private Wrapper _wrapper;

    /**
     * @return the wrapper
     */
    @Override
    public Wrapper getWrapper() {
        if (_wrapper == null) {
            Class<?> thisClass = getClass();
            Class<? extends Wrapper> wrapperClass = getWrapperClass(thisClass);
            if (wrapperClass != null) {
                Class<?> parameterType = XS1.getConstructorParameterType(wrapperClass, thisClass);
                if (parameterType != null) {
                    String pattern = "failed to wrap {0} using {1}({2})";
                    String message = MessageFormat.format(pattern, thisClass, wrapperClass, parameterType);
                    try {
                        _wrapper = wrapperClass.getConstructor(parameterType).newInstance(this);
                    } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                        throw new RuntimeException(message, ex);
                    }
                } else {
                    String pattern = "{1} is not a valid wrapper for {0}";
                    String message = MessageFormat.format(pattern, thisClass, wrapperClass);
                    throw new RuntimeException(message);
                }
            }
        }
        return _wrapper;
    }

    @SuppressWarnings("unchecked") // unchecked cast
    private Class<? extends Wrapper> getWrapperClass(Class<?> clazz) {
        if (clazz != null && Wrappable.class.isAssignableFrom(clazz)) {
            Class<? extends Wrappable> wrappableClass = (Class<? extends Wrappable>) clazz; // unchecked cast
            Class<? extends Wrapper> wrapperClass = TLB.getWrapperClass(wrappableClass);
            return wrapperClass != null ? wrapperClass : getWrapperClass(clazz.getSuperclass());
        }
        return getDefaultWrapperClass();
    }

    /**
     * @return the default wrapper class
     */
    @Override
    public Class<? extends ArtifactWrapper> getDefaultWrapperClass() {
        return ArtifactWrapper.class;
    }

    // <editor-fold defaultstate="collapsed" desc="toString">
    @Override
    public String toString() {
        String str1 = _name;
        String str2 = getNamedClass().getSimpleName();
        String str3 = str1 == null || str1.equals(str2) ? "" : "[" + str1 + "]";
        return str2 + str3 + "@" + Integer.toHexString(hashCode());
    }

    @Override
    public String toString(int n) {
        return toString(n, null);
    }

    @Override
    public String toString(int n, String key) {
        return toString(n, key, Project.isVerbose());
    }

    @Override
    public String toString(int n, String key, boolean verbose) {
        return toString(n, key, verbose, verbose, verbose);
    }

    @Override
    public String toString(int n, String key, boolean verbose, boolean fields, boolean maps) {
        String r4n = StringUtils.repeat(" ", 4 * n);
        String tab = verbose ? StringUtils.repeat(" ", 4) : "";
        String fee = verbose ? StringUtils.repeat(tab, n) : "";
//      String faa = " = ";
        String foo = verbose ? EOL : "";
        String eol = verbose ? EOL : ", ";
        String string = EOL;
        String c = classToString(n, key, verbose);
        String f = fieldsToString(n, key, verbose, fields, maps);
        String m = mapsToString(n, key, verbose, fields, maps);
        string += c;
        string += " " + "{" + " " + this + eol;
        string += f;
        string += m;
        if (!verbose) {
            string = StringUtils.removeEnd(string, eol);
            if (c.contains(EOL) || f.contains(EOL) || m.contains(EOL)) {
                fee = r4n;
            }
        }
        string += fee + "}" + EOL;
        return string.replaceAll(EOL + EOL, EOL);
    }

    protected String classToString(int n, String key, boolean verbose) {
        String tab = StringUtils.repeat(" ", 4);
        String fee = StringUtils.repeat(tab, n);
//      String faa = " = ";
//      String foo = EOL;
        String string = "";
        String s1 = getDeclaringField() == null ? "" : getDeclaringField().getType().getSimpleName();
        String s2 = getNamedClass().getSimpleName();
        String s3 = StringUtils.isBlank(s1) || s1.equals(s2) ? s2 : StringUtils.removeEnd(s1, "[]") + " " + "(" + s2 + ")";
        String s4 = StringUtils.isBlank(key) ? "" : " " + key;
//      String s5 = "@" + Integer.toHexString(hashCode());
//      string += fee + s3 + s4 + s5;
        string += fee + s3 + s4;
        return string;
    }

    protected String fieldsToString(int n, String key, boolean verbose, boolean fields, boolean maps) {
        String tab = verbose ? StringUtils.repeat(" ", 4) : "";
        String fee = verbose ? StringUtils.repeat(tab, n) : "";
        String faa = " = ";
        String foo = verbose ? EOL : ", ";
        String string = "";
        if (fields || verbose) {
            if (verbose) {
                if (_declaringArtifact != null) {
                    string += fee + tab + "declaringArtifact" + faa + _declaringArtifact + foo;
                    if (_declaringField != null) {
                        string += fee + tab + "declaringField" + faa + _declaringField + foo;
                        if (_declaringField.getType().isArray()) {
                            string += fee + tab + "declaringFieldIndex" + faa + _declaringFieldIndex + foo;
                        }
                    }
                    string += fee + tab + "path" + faa + getPathString() + foo;
                }
                if (this instanceof Entity) {
                    string += fee + tab + "depth" + faa + _depth + foo;
                    string += fee + tab + "round" + faa + _round + foo;
                }
                if (_name != null) {
                    string += fee + tab + "name" + faa + _name + foo;
                }
                if (_alias != null) {
                    string += fee + tab + "alias" + faa + _alias + foo;
                }
//              string += fee + tab + "defaultLabel" + faa + _defaultLabel + foo;
//              string += fee + tab + "defaultShortLabel" + faa + _defaultShortLabel + foo;
//              string += fee + tab + "defaultDescription" + faa + _defaultDescription + foo;
//              string += fee + tab + "defaultShortDescription" + faa + _defaultShortDescription + foo;
//              string += fee + tab + "defaultTooltip" + faa + _defaultTooltip + foo;
            } else {
                string += fee + tab + "path" + faa + getPathString() + foo;
            }

        }
        return string;
    }

    protected String mapsToString(int n, String key, boolean verbose, boolean fields, boolean maps) {
        return "";
    }
    // </editor-fold>

}
